//------------------------------------------------------------------------------
// X2D Game Engine
// Copyright (C) 2006, 2007, 2008 Janusz Kowalski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include <cassert>
#include <cstdio>

#include "pmsg.h"

#include "x2d_View.h"
#include "x2d_ext.h"
#include "global.h"

namespace x2d {
  
View::View(int x, int y, int w, int h, World *world) : Widget(x,y,w,h) {
  world_ = world;
  dx=-w/2; dy=-h/2;
  velX_=0; velY_=0;
  accelX_=0; accelY_=0;
  entityToFollow=0;
  for(int i=0; i<3; i++) planeVisible_[i]=true;
  followMode_ = VIEW_XFOLLOW;
}

int View::getVShift(int mx, int my, int mz) const {
  Map::AttrT attr = world_->getMap()->getAttr(mx, my, mz);
  if(attr&Map::ATTR_SHU) return -16;
  if(attr&Map::ATTR_SHD) return 16;
  return 0;
}

int View::getHShift(int mx, int my, int mz) const {
  Map::AttrT attr = world_->getMap()->getAttr(mx, my, mz);
  if(attr&Map::ATTR_SHL) return -16;
  if(attr&Map::ATTR_SHR) return 16;
  return 0;
}

void View::follow(Entity *ent) {
  entityToFollow=ent;
}

void View::setFollowMode(int mode) {
  followMode_ = mode;
}

#define TIME_DELAY 10
#define K_A 0.0003f
#define K_B 0.008f

// Movement, animations, etc. This function should be caled in constant
// time intervals equal to TIME_DELAY.
void View::move() {
  float centerX = dx + getW()/2;
  float centerY = dy + getH()/2;
  
  if(followMode_ == VIEW_XFOLLOW) {
    // Set accelerations.
    accelX_ = K_A*(targetX_-centerX) - K_B*(velX_);
    accelY_ = K_A*(targetY_-centerY) - K_B*(velY_);
    
    // Set velocities.
    velX_+=accelX_*TIME_DELAY; velY_+=accelY_*TIME_DELAY;
  }
  
  // Apply velocities to position of the view.
  dx+=velX_; dy+=velY_;
}

int scaled(int d) {
  return static_cast<int>(d/OP_MAP_DIST_SCALE);
}

void View::draw(GraphicContext &g) {
  g.setClipping(true);
  
  // The new background-drawing code
  for(unsigned i=0; i<world_->getBgScrapList()->size(); i++) {
    BgScrap &scrap=(*world_->getBgScrapList())[i];
    int deltaX = static_cast<int>(-scrap.scaleX_*scaled(dx+getW()/2)+getW()/2+scrap.x_);
    int deltaY = static_cast<int>(-scrap.scaleY_*scaled(dy+getH()/2)+getH()/2+scrap.y_);
    
    if(scrap.repeatX_>0) {deltaX%=scrap.repeatX_; if(deltaX>0) deltaX-=scrap.repeatX_;}
    if(scrap.repeatY_>0) {deltaY%=scrap.repeatY_; if(deltaY>0) deltaY-=scrap.repeatY_;}
    
    int deY = deltaY;
    do {
      int deX = deltaX;
      do {
        g.blitImage(scrap.image_, deX, deY);
        deX += scrap.repeatX_;
      } while(scrap.repeatX_ && deX<getW());
      deY += scrap.repeatY_;
    } while (scrap.repeatY_ && deY<getH());
  }
  
  // !! What about the visibility of map layers?

  drawMapPlane(g,PLANE_ACTION);
  
  if(hasFocus()==false) {
    g.setColor(128,128,128,128);
    g.filledRectangle(0,0,getW(),getH());
  }
  
  g.setClipping(false);
}

void View::event(x2tk::EV ev) {
  pmsgDebugF("Event! %d\n", (int)hasFocus());
  if(ev==x2tk::EV_FOCUS) {
    if(hasFocus()) world_->setPause(false);
    else world_->setPause(true);
  }
  Widget::event(ev);
}

#define HMARGIN 16
#define VMARGIN 16

// Pixels per meter.
#define X2D_PPM 64

void View::drawMapPlane(GraphicContext &g, int plane) {

  // Totally new drawing approach. We use AABB query from Box2D to get a list
  // of visible shapes.
  
  b2AABB aabb;
  aabb.lowerBound.Set(dx/float32(X2D_PPM),dy/float32(X2D_PPM));
  aabb.upperBound.Set((dx+getW())/float32(X2D_PPM), (dy+getH())/float32(X2D_PPM));
  
  // !! Check this size.
  b2Shape* buffer[128];
  int32 count = world_->getB2World()->Query(aabb,buffer,128);
  
  for(int i=0;i<count;i++) {
    if(buffer[i]->GetUserData()) {
      // This should be different.
      if(buffer[i]->GetType()==e_polygonShape) {
        PolyShapeObject *poly = static_cast<PolyShapeObject*>(buffer[i]->GetUserData());
        b2Vec2 xy = buffer[i]->GetBody()->GetPosition();
        poly->draw(g,xy.x*X2D_PPM-dx,xy.y*X2D_PPM-dy);
      }
    }
  }
  
  // Draw other details (body positions).
  g.startOpenGL();
  glDisable(GL_TEXTURE_2D);
  glBegin(GL_LINES);
  glColor4f(0,1,0,1);
  for(int i=0;i<count;i++) {
    if(buffer[i]->GetType()==e_polygonShape) {
      b2PolygonShape *poly = static_cast<b2PolygonShape*>(buffer[i]);
      b2Vec2 xy = poly->GetBody()->GetPosition(); xy*=X2D_PPM;
      for(int j=0; j<poly->GetVertexCount(); j++) {
        glVertex2f(xy.x-dx, xy.y-dy);
        glVertex2f(poly->GetVertices()[j].x *X2D_PPM+xy.x-dx, poly->GetVertices()[j].y*X2D_PPM+xy.y-dy);
      }
    }
  }
  glEnd();
  g.endOpenGL();
}

void View::lookAt(int x, int y) {
  targetX_ = x; targetY_ = y;
}



//-----------------------------------------------------------------------------
// Class EditView
//-----------------------------------------------------------------------------

EditView::EditView(int x, int y, int w, int h, World *world) : View(x,y,w,h,world)
{
  currentPlane_=1;
  imgGrid_ = Image(X2D_PATH_TILESET "MapGrid.png");
  imgDiag_ = Image(X2D_PATH_TILESET "diag.png");
  setFollowMode(VIEW_NOFOLLOW);
  selW_=1; selH_=1; selBuffer_.add(0);
  pasteMode_=false;
  selMode_=false;
  history_new = true;
  formId_=0;
  editMode_=EM_CONVEX;
  // We don't have to delete this one.
  defaultDrawPolyObject_=new DrawPolyObject(*world_);
}

EditView::~EditView() {
}

void EditView::edit_paste(int mapX, int mapY) {
  history_group();
  
  for(int y=0; y<abs(selH_); y++) {
    for(int x=0; x<abs(selW_); x++) {
      int ex = (selW_>0?mapX+x:mapX-x);
      int ey = (selH_>0?mapY+y:mapY-y);
      int formId = world_->getMap()->get(ex,ey,currentPlane_);
      
      if(formId!=selBuffer_[y*abs(selW_)+x]) {
        history_add(ex, ey, currentPlane_, formId);
        world_->getMap()->set(ex, ey, currentPlane_, selBuffer_[y*abs(selW_)+x]);
      }
    }
  }
}

void EditView::history_add(int x, int y, int plane, int v) {
  if(history_new || history_.size()==0) {
    history_new = false;
    history_.add(RawVector<XYZV>());
    if(history_.size()>8) {
      history_.removeElementAt(0);
    }
  }
  
  XYZV xyzv = {x,y,plane,v};
  history_.lastElement().add(xyzv);
}

void EditView::history_group() {
  history_new = true;
}

void EditView::history_undo() {
  if(history_.size()>0) {
    int index = history_.size()-1;
    RawVector<XYZV> &last = history_[index];
    
    for(unsigned i=0; i<last.size(); i++) {
      world_->getMap()->set(last[i].x, last[i].y, last[i].z, last[i].v);
    }
    
    history_.removeElementAt(index);
  }
}


bool EditView::mouseDown(int type) {
  bool stat = Widget::mouseDown(type);
  
  if(!stat) return false;
  
  switch(editMode_) {
    case EM_TEST: {
      float x = getMouseX() + dx, y = getMouseY() + dy;
      
      if(type==1) {
        world_->createEntity(formId_, x, y);
      } else if(type==4) {
        formId_=world_->getFormList()->getNextFormId(formId_,0);
      } else if(type==5) {
        formId_=world_->getFormList()->getPreviousFormId(formId_,0);
      }
      return true;
    } break;
    
    case EM_CONVEX: {
      IntXY xy(editPointX_,editPointY_);
      std::vector<IntXY> &v = vertList_;

      if(type==1) {
        if(v.size()>2 && inside) {
          b2PolygonDef shapeDef;
          std::vector<IntXY> &v = vertList_;

          // !! Where exactly is the shape created? At (dx,dy)??
        
          if(isCCW) {
            for(unsigned i=0; i<v.size(); i++) {
              shapeDef.vertices[i].Set((v[i].x-dx)/float32(X2D_PPM), (v[i].y-dy)/float32(X2D_PPM));
            }
          } else {
            for(unsigned i=0; i<v.size(); i++) {
              shapeDef.vertices[v.size()-i-1].Set((v[i].x-dx)/float32(X2D_PPM), (v[i].y-dy)/float32(X2D_PPM));
            }
          }
          shapeDef.vertexCount=v.size();
          // Prepare body.
          b2BodyDef bodyDef;
          bodyDef.position.Set(dx/float32(X2D_PPM), dy/float32(X2D_PPM));
          // Create body.
          BodyObject *b = new BodyObject(*world_,&bodyDef,true);
          // Create shape.
          PolyShapeObject *p = new PolyShapeObject(*world_,shapeDef);
          
          xconnect(p,0,b);
          if(defaultDrawPolyObject_) {
            xconnect(p,1,defaultDrawPolyObject_);
          }
        
          vertList_.clear();
        } else if(vertList_.size()<b2_maxPolygonVertices) {
          // Make sure we are inside the world's AABB
          b2AABB aabb = world_->getB2World()->m_broadPhase->m_worldAABB;
          aabb.lowerBound*=X2D_PPM; aabb.upperBound*=X2D_PPM;
          if(xy.x>aabb.upperBound.x) break;
          if(xy.x<aabb.lowerBound.x) break;
          if(xy.y>aabb.upperBound.y) break;
          if(xy.y<aabb.lowerBound.y) break;
          
          // Make sure we are not too close to the previous point
          if(v.size()>0) {
            IntXY xyp = v[v.size()-1];
            if(sqrt((xyp.x-xy.x)*(xyp.x-xy.x)+(xyp.y-xy.y)*(xyp.y-xy.y))<8) break;
          }
          
          // Make sure we are not too close to the last point
          if(v.size()>1) {
            IntXY xyp = v[0];
            if(sqrt((xyp.x-xy.x)*(xyp.x-xy.x)+(xyp.y-xy.y)*(xyp.y-xy.y))<8) break;
          }
          
          vertList_.push_back(xy);
          
            // If polygon is not convex - remove verticle.
          if(vertList_.size()>2) {
            std::vector<IntXY> &v = vertList_;
            bool remove=false;
            int sign=0;
            for(unsigned i=0; !remove && i<v.size(); i++) {
              int i1=i, i2=(i+1)%v.size(), i3=(i+2)%v.size();
              int p = (v[i2].x-v[i1].x)*(v[i3].y-v[i2].y);
              p-=(v[i2].y-v[i1].y)*(v[i3].x-v[i2].x);
              if(p==0) remove=true;
              else {
                if(sign==0) sign=(p>0?1:-1);
                remove=(sign*p<0);
              }
            }
            if(remove) vertList_.pop_back();
            isCCW=(sign>0);
          }
        }
      } else if(type==2) {
        if(v.size()>0) v.pop_back();
      }
    } break;
    
  }

  return stat;
}

void EditView::move() {
  View::move();
  int mouseX = getMouseX(), mouseY = getMouseY();

  // Set editPointX_ and editPointY_ values.
  //   Grid snapping.
  int px=mouseX+dx, py=mouseY+dy;
  int gx=ceilDiv(px-32/2,32)*32;
  int gy=ceilDiv(py-32/2,32)*32;
  if(abs(px-gx)<6 && abs(py-gy<6)) {
    editPointX_=gx; editPointY_=gy;
  } else {
    editPointX_=px; editPointY_=py;
  }

  //   Vertex snapping.
  // !! Improve this code.
  b2AABB aabb;
  aabb.lowerBound.Set((px-5)/float32(X2D_PPM),(py-5)/float32(X2D_PPM));
  aabb.upperBound.Set((px+5)/float32(X2D_PPM),(py+5)/float32(X2D_PPM));

  b2Shape* buffer[16];
  int32 count = world_->getB2World()->Query(aabb,buffer,16);
  for(;count--;) {
    if(buffer[count]->GetType()==e_polygonShape) {
      b2PolygonShape *poly = static_cast<b2PolygonShape*>(buffer[count]);
      for(int i=poly->GetVertexCount(); i--;) {
        b2Vec2 vert = poly->GetVertices()[i];
        vert*=X2D_PPM; // Scale
        if(sqrt((vert.x-px)*(vert.x-px)+(vert.y-py)*(vert.y-py))<5) {
          editPointX_=vert.x; editPointY_=vert.y;
        }
      }
    }
  }


  // Check if mouse pointer is inside the convex being edited.
  if(editMode_==EM_CONVEX) {
    int x = getMouseX() + dx, y = getMouseY() + dy;
      IntXY xy(x,y);
      std::vector<IntXY> &v = vertList_;

      // Check if we are inside current convex.
      if(v.size()>2) {
        inside = true;

        for(int i=0; inside && i<v.size(); i++) {
          IntXY A(v[i]);
          IntXY B(v[(i+1)%v.size()]);

          int s = (A.y-B.y)*(xy.x-A.x)+(B.x-A.x)*(xy.y-A.y);

          if(isCCW) {
            if(s<=0) inside=false;
          } else {
            if(s>=0) inside=false;
          }
        }
      }
  }
  
  if(pasteMode_ && mouseX>=0 && mouseX<getW() && mouseY>=0 && mouseY<getH()) {
    int mapX = floorDiv(mouseX+dx,32);
    int mapY = floorDiv(mouseY+dy,32);
    
    mapX = floorDiv(mapX-pasteX_,abs(selW_))*abs(selW_)+pasteX_;
    mapY = floorDiv(mapY-pasteY_,abs(selH_))*abs(selH_)+pasteY_;
    
    edit_paste(mapX, mapY);
  }
  
  if(selMode_ && mouseX>=0 && mouseX<getW() && mouseY>=0 && mouseY<getH()) {
    int mapX = floorDiv(mouseX+dx,32);
    int mapY = floorDiv(mouseY+dy,32);
    
    if(mapX>=selX_) {
      selW_=mapX-selX_+1;
      //if(selW_>4) selW_=4;
    } else {
      selW_=mapX-selX_-1;
      //if(selW_<-4) selW_=-4;
    }
    
    if(mapY>=selY_) {
      selH_=mapY-selY_+1;
      //if(selH_>4) selH_=4;
    } else {
      selH_=mapY-selY_-1;
      //if(selH_<-4) selH_=-4;
    }
  }
}

void EditView::mouseUp(int type) {
  if(type==1) pasteMode_=false;
  else if(type==2) {
    selBuffer_.clear();
    for(int y=0; y<abs(selH_); y++)
      for(int x=0; x<abs(selW_); x++)
        selBuffer_.add(world_->getMap()->get((selW_>0?selX_+x:selX_-x), (selH_>0?selY_+y:selY_-y), currentPlane_));
    selMode_=false;
  }
}

bool isWSADKey(SDL_keysym keysym) {
  if(keysym.sym==SDLK_w) return true;
  if(keysym.sym==SDLK_s) return true;
  if(keysym.sym==SDLK_a) return true;
  if(keysym.sym==SDLK_d) return true;
  return false;
}

void EditView::event_key(const SDL_KeyboardEvent& key) {
  // Keays requiring Ctrl modifier.
  if((key.keysym.mod&KMOD_LCTRL) || (key.keysym.mod&KMOD_RCTRL)) {
    if(key.type==SDL_KEYDOWN) {
      switch(key.keysym.sym) {
        case SDLK_s:
          //world_->getMap()->save(X2D_PATH_MAPS "default.map");
          world_->save("/tmp/test.map");
          break;
        case SDLK_l:
          //world_->getMap()->load(X2D_PATH_MAPS "default.map"); clearHistory();
          world_->load("/tmp/test.map");
          break;
        case SDLK_c:
          world_->getMap()->clear();
          break;
      }
    }
  }
  
  
  if((key.keysym.mod==KMOD_NONE) && (key.type==SDL_KEYDOWN)) {
    switch(key.keysym.sym) {
       case SDLK_d:
          setVelocityX(8);
          break;
        case SDLK_a:
          setVelocityX(-8);
          break;
        case SDLK_w:
          setVelocityY(-8);
          break;
        case SDLK_s:
          setVelocityY(8);
          break;
        case SDLK_1:
          setCurrentPlane(0);
          break;
        case SDLK_2:
          setCurrentPlane(1);
          break;
        case SDLK_3:
          setCurrentPlane(2);
          break;
        case SDLK_4:
          setCurrentPlane(3);
          break;
        case SDLK_u:
          undo();
          break;
    }
  }
  
  if(key.type==SDL_KEYUP) {
    switch(key.keysym.sym) {
      case SDLK_d:
      case SDLK_a:
        setVelocityX(0);
        break;
      case SDLK_w:
      case SDLK_s:
        setVelocityY(0);
        break;
    }
  }
  
  if(key.keysym.mod == KMOD_LALT) {
    if(key.type==SDL_KEYDOWN && isWSADKey(key.keysym)) {
      // Calculate map position of the mouse and get attributes of the
      // pointed map field.
      int mx = floorDiv(getMouseX()+dx,32);
      int my = floorDiv(getMouseY()+dy,32);
      Map::AttrT attr = world_->getMap()->getAttr(mx,my,currentPlane_);
      // Continue according to the key pressed.
      switch(key.keysym.sym) {
        case SDLK_w:
          pmsgDebugF("SDLK_UP START (%x)\n", (unsigned)attr);
          if(attr&Map::ATTR_SHD) attr &= ~Map::ATTR_SHD;
          else attr |= Map::ATTR_SHU;
          pmsgDebugF("SDLK_UP END   (%x)\n", (unsigned)attr);
          break;
        case SDLK_s:
          if(attr&Map::ATTR_SHU) attr &= ~Map::ATTR_SHU;
          else attr |= Map::ATTR_SHD;
          pmsgDebugF("SDLK_DOWN\n");
          break;
        case SDLK_a:
          if(attr&Map::ATTR_SHR) attr &= ~Map::ATTR_SHR;
          else attr |= Map::ATTR_SHL;
          break;
        case SDLK_d:
          if(attr&Map::ATTR_SHL) attr &= ~Map::ATTR_SHL;
          else attr |= Map::ATTR_SHR;
          break;
      }
      // Set new map field attribute.
      world_->getMap()->setAttr(mx, my, currentPlane_, attr);
    }
  }
}

void EditView::draw(GraphicContext &g) {
  View::draw(g);
  
  // Draw some objects that are visible only in edit mode.
  drawMapPlane(g, PLANE_MAGIC);
  

  // Draw gid.
  const int gridResX = 32, gridResY = 32;

  g.startOpenGL();

  glLineWidth(2.0f);
  glLineStipple(4, 0xAAAA);
  glDisable(GL_TEXTURE_2D);

  glBegin(GL_LINES);
  glColor4f(0.0f,0.0f,0.0f,0.5f);
  for(int x=ceilDiv(dx,32)*32-dx; x<=getW(); x+=gridResX) {
    glVertex2i(x,0);
    glVertex2i(x,getH());
  }

  for(int y=ceilDiv(dy,32)*32-dy; y<=getH(); y+=gridResY) {
    glVertex2i(0,y);
    glVertex2i(getW(),y);
  }
  glEnd();

  g.endOpenGL();
  
  // Draw selection.
  if(selMode_) {
    for(int y=0; y<abs(selH_); y++) {
      for(int x=0; x<abs(selW_); x++) {
        g.blitImage(imgDiag_, (selW_>0?selX_+x:selX_-x)*32-dx,(selH_>0?selY_+y:selY_-y)*32-dy);
      }
    }
    
    // !! What is this code for??
    for(int my=selY_; my<selY_+selH_; my++)
      for(int mx=selX_; mx<selX_+selW_; mx++)
        g.blitImage(imgDiag_, 32*mx-dx, 32*my-dy);
  }
  
  // Finally, draw current entity.
  EntityHandler *eh = world_->getFormList()->getEntityHandler(formId_);
  eh->draw(g,getMouseX(),getMouseY());
  
  std::vector<IntXY> &v = vertList_;

  // Draw some other edit-mode stuff...

  g.startOpenGL(); // Drawing will be done in OpenGL to make it faster.
  glDisable(GL_TEXTURE_2D);

  // Draw currently edited polygon (lines and points).
  if(v.size()>0) {
    // Draw polygon.
    if(v.size()>2 && inside) {
      glBegin(GL_POLYGON);
      glColor4f(1.0f,0.0f,0.0f,0.5f);
      for(int i=0; i<v.size(); i++) {
        glVertex2i(v[i].x-dx, v[i].y-dy);
      }
      glEnd();
    }

    // Draw lines.
    if(v.size()>1) {
      glBegin(GL_LINE_LOOP);
      glColor4f(1,1,1,1);
      for(int i=0; i<v.size(); i++) {
        glVertex2i(v[i].x-dx, v[i].y-dy);
      }
      glEnd();

      if(v.size()>2) {
        glLineStipple(4, 0xAAAA);
        glEnable(GL_LINE_STIPPLE);
        glBegin(GL_LINES);
        glColor4f(0,1,0,1);
        glVertex2i(v[0].x-dx, v[0].y-dy);
        glVertex2i(v[v.size()-1].x-dx, v[v.size()-1].y-dy);
        glEnd();
        glDisable(GL_LINE_STIPPLE);
      }
    }

    // Draw poins.
    glPointSize(4);
    glBegin(GL_POINTS);
    glColor4f(1,1,1,1);
    for(unsigned i=0; i<v.size(); i++) {
      glVertex2i(v[i].x-dx, v[i].y-dy);
    }
    glEnd();
  }

  // Draw world's AABB.
  b2AABB aabb = world_->getB2World()->m_broadPhase->m_worldAABB;
  aabb.lowerBound *= 64.0f; aabb.upperBound *= 64.0f;
  bool tf=false, bf=false, lf=false, rf=false;
  float x1,y1,x2,y2;
  if(aabb.lowerBound.x>dx) { lf=true; x1=min(dx+getW(), aabb.lowerBound.x); } else x1=dx;
  if(aabb.upperBound.x<dx+getW()) { rf=true; x2=max(dx,aabb.upperBound.x); } else x2=dx+getW();
  if(aabb.lowerBound.y>dy) { tf=true; y1=min(dy+getH(), aabb.lowerBound.y); } else y1=dy;
  if(aabb.upperBound.y<dy+getH()) { bf=true; y2=max(dy, aabb.upperBound.y); } else y2=dy+getH();

  static bool x=false;
  if(!x) {
    x=true;
    printf("%f %f %f %f\n", aabb.lowerBound.x, aabb.lowerBound.y, aabb.upperBound.x, aabb.upperBound.y);
  }

  glBegin(GL_QUADS);
  glColor4f(0,0,0,0.5);

  if(lf) {
    glVertex2f(x1-dx, y2-dy);
    glVertex2f(x1-dx, y1-dy);
    glVertex2f(0, 0);
    glVertex2f(0, getH());
  }

  if(rf) {
    glVertex2f(x2-dx,y1-dy);
    glVertex2f(x2-dx,y2-dy);
    glVertex2f(getW(),getH());
    glVertex2f(getW(),0);
  }

  if(tf) {
    glVertex2f(x1-dx,y1-dy);
    glVertex2f(x2-dx,y1-dy);
    glVertex2f(getW(),0);
    glVertex2f(0,0);
  }

  if(bf) { 
    glVertex2f(x2-dx,y2-dy);
    glVertex2f(x1-dx,y2-dy);
    glVertex2f(0,getH());
    glVertex2f(getW(),getH());
  }

  glEnd();

  // Draw center mark.
  const int RA=1, RB=32;
  glBegin(GL_QUADS);
  glColor4f(1,0,0,1);
  // Vertical part.
  glVertex2i(RA-dx,RB-dy);
  glVertex2i(RA-dx,-RB-dy);
  glVertex2i(-RA-dx,-RB-dy);
  glVertex2i(-RA-dx,RB-dy);
  // Horizontal part.
  glVertex2i(RB-dx,RA-dy);
  glVertex2i(RB-dx,-RA-dy);
  glVertex2i(-RB-dx,-RA-dy);
  glVertex2i(-RB-dx,RA-dy);
  glEnd();

  // Draw special indicator at the mouse pointer.
  glBegin(GL_QUADS);
  glColor4f(1,1,1,1);
  glVertex2i(editPointX_-dx+1,editPointY_-dy+1);
  glVertex2i(editPointX_-dx-1,editPointY_-dy+1);
  glVertex2i(editPointX_-dx-1,editPointY_-dy-1);
  glVertex2i(editPointX_-dx+1,editPointY_-dy-1);
  glEnd();

  g.endOpenGL();
}

GameView::GameView(int x, int y, int w, int h, World *world) : View(x,y,w,h,world) {
}

void GameView::event_key(const SDL_KeyboardEvent& key) {
  Widget::event_key(key);
  
  if(plEnt_==0) return;
  
  bool value = (key.type==SDL_KEYDOWN?true:false);
  switch(key.keysym.sym) {
    case SDLK_RIGHT:
      plEnt_->goRight(value); break;
    case SDLK_LEFT:
      plEnt_->goLeft(value); break;
    case SDLK_UP:
      plEnt_->jump(value); break;
  }
}

bool GameView::mouseDown(int type) {
  bool stat = Widget::mouseDown(type);
  if(!stat) return false;
  try {
    new PhyCircleEnt(world_,getMouseX()+dx,getMouseY()+dy,16+rand()%16);
  } catch(...) { }
  return stat;
}

} // namespace x2d
